/*
 * executable modules analyzer
 *
 * Copyright (c) 2006-2008 Gianluigi Tiesi <sherpya@netfarm.it>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this software; if not, write to the
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <exeScanner.h>
#include <safe_ctype.h>

/* -1 = wildchar - -2 = stop here */
sigs_t signatures[] =
{
    { { 0x60, 0xbe,   -1,   -1,   -1,   -1, 0x8d, 0xbe,   -1,   -1,   -1, 0xff, 0x57, -2 },                "UPX",                  .0f },
    { { 0x94, 0xbc, 0x5d, 0x07, 0x42, 0x00, 0xb9, 0x1d, 0x00, 0x00, 0x00, 0x80, 0x34, 0x0c, 0x44, 0xe2 },  "UPXSHiT",              .0f },
    { { 0xbe, 0xa4, 0x01, 0x40, 0x00, 0xad, 0x93, 0xad, 0x97, 0xad, 0x56, 0x96, 0xb2, 0x80, 0xa4, 0xb6 },  "FSG 1.33",             .0f },
    { { 0x4d, 0x5a,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00 },  "FSG 2.00",             .0f },
    { { 0x4d, 0x5a, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c, 0x33, 0x32, 0x2e, 0x44, 0x4c, 0x4c, 0x00, 0x00 },  "WinUpack 0.39",        .0f },
    { { 0xbe, 0x88, 0x01, 0x40, 0x00, 0xad, 0x8b, 0xf8, 0x95, 0xad, 0x91, 0xf3, 0xa5, 0xad, 0xb5, 0x1c },  "Upack 2.4/2.9",        .0f },
    { { 0xbe, 0x48, 0x01, 0x40, 0x00, 0xad, 0x8b, 0xf8, 0x95, 0xa5, 0x33, 0xc0, 0x33, 0xc9, 0xab, 0x48 },  "Upack 1.1/1.2",        .0f },
    { { 0x83, 0xec, 0x20, 0x53, 0x55, 0x56, 0x33, 0xdb, 0x57, 0x89, 0x5c, 0x24, 0x18, 0xc7, 0x44, 0x24 },  "NullSoft PiMP",        .0f },
    { { 0xe9,   -1,   -1,   -1, 0xff, 0x0c,   -1,   -1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },  "Mew 11 1.2",           .0f },
    { { 0x60, 0xe9, 0x3d, 0x04, 0x00, 0x00,   -2 },                                                        "ASPack 2.11",          .0f },
    { { 0x60, 0xe8, 0x03, 0x00, 0x00, 0x00, 0xe9, 0xeb, 0x04, 0x5d, 0x45, 0x55, 0xc3, 0xe8, 0x01, 0x00 },  "ASPack 2.12",          .0f },
    { { 0x55, 0x83, 0xc4, 0x04, 0x76, 0x08, 0x7a, 0x06, 0x74, 0x04, 0x66, 0x83, 0xea, 0x00, 0xf5, 0x50 },  "Morphine 1.4/2.7",     .0f },
    { { 0x56, 0x72, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x0b, 0xd2, 0xf9, 0x84, 0xdb, 0x68, 0x34 },  "Morphine 1.4/2.7 [2]", .0f },
    { { 0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x8b, 0xd5, 0x81, 0xed },  "PEDiminisher 0.1",     .0f },

    { { 0xe8, 0xf6, 0x03, 0x00, 0x00, 0xe9, 0x9e, 0xfd, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc },  "MSVC8 Release",        -1.0f },
    { { 0xe9,   -1,   -1,   -1, 0x00, 0xe9,   -1,   -1,   -1, 0x00, 0xe9,   -1,   -1,   -1, 0x00, 0xe9 },  "MSVC8 Debug",          -1.0f },

    { { 0xe8,   -1,   -1, 0x00, 0x00, 0xe9, 0x16, 0xfe, 0xff, 0xff, -2 },                                  "MSVC6 Release",        -2.0f },

    { { 0xe9, 0x96, 0xee, 0x0e, 0x00, 0xb8, 0x6c, 0x02, 0x58, 0x00, 0xe8, 0xae, 0xe4, 0x0e, 0x00, 0x83 },  "MSVC6 Release (2)",    -1.0f },
    { { 0x55, 0x8b, 0xec, 0x6a, 0xff, 0x68, 0xb0, 0x41, 0x40, 0x00, 0x68, 0x10, 0x36, 0x40, 0x00, 0x64 },  "MSVC6 Release (3)",    -1.0f },
    { { 0x55, 0x8b, 0xec, 0x53, 0x8b, 0x5d, 0x08, 0x56, 0x8b, 0x75, 0x0c, 0x57, 0x8b, 0x7d, 0x10, 0x85 },  "MSVC6 Release (4)",    -1.0f },

    { { 0x83, 0x7c, 0x24, 0x08, 0x01, 0x75, 0x05, 0xe8,   -1,   -1, 0x00, 0x00, 0xff, 0x74, 0x24, 0x04 },  "MSVC6 Release DLL",    -1.0f },
    { { 0xff, 0x25,   -1,   -1,   -1,   -1, 0xcc, 0xcc, 0x03, 0x30, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00 },  "DotNet",               -1.0f },
    { { 0x55, 0x89, 0xe5,   -2 },                                                                          "MinGW",                -1.0f },
    { { 0 }, 0, 0 }
};

int sigcmp(const uint8_t *data, const int16_t *sig, size_t n)
{
    uint8_t *d = (uint8_t *) data;
    int16_t *s = (int16_t *) sig;
    while (n-- != 0)
    {
        if (*s == -2) return 0;
        if ((*s != -1) && (*d != *s))
            return (*d < *s) ? -1 : +1;
        d++;
        s++;
    }
    return 0;
}

sigs_t *checksig(uint8_t *data)
{
    int i = 0;
    while (signatures[i].name)
    {
        if (!sigcmp(data, signatures[i].sig, 16))
            return &signatures[i];
        i++;
    }
    return NULL;
}

double calc_entropy(const unsigned char *data, size_t size)
{
    double entropy = .0f;
    size_t p[256];
    size_t c, i;

    memset(p, 0, sizeof(p));

    for (c = 0; c < size; c++)
        p[data[c]]++;

    for (i = 0; i < 256; i++)
        if (p[i])
            entropy -= ((double) p[i] / size) * log((double) p[i] / size);
    return entropy;
}

#define FILLBYTES(dst) \
    if (IsBadReadPtr(seek, sizeof(dst))) \
    { \
        cli_errmsg("exeScanner: Bad pointer!!!\n"); \
        goto cleanup; \
    } \
    memcpy(&dst, seek, sizeof(dst));

/* Packed exe heuristic detection, errors are handled as like of non packed data */
int is_packed(const char *filename)
{
    int packed = 0;
    int i = 0, c = 0;
    int badsection = 0;
    double entropy = 0.0;
    sigs_t *sig = NULL;
    uint16_t e_mz;
    uint32_t e_lfanew, e_magic;
    uint32_t epoff = 0;
    unsigned char *seek = NULL, *s_start = NULL, *ep = NULL, *lpMapAddress = NULL;
    PIMAGE_FILE_HEADER pehdr;
    PIMAGE_OPTIONAL_HEADER32 opthdr;
    PIMAGE_SECTION_HEADER sechdr;
    char secname[IMAGE_SIZEOF_SHORT_NAME];

    HANDLE hFile = INVALID_HANDLE_VALUE, hMapFile = NULL;

    hFile = CreateFileA(filename, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        cli_dbgmsg("exeScanner: CreateFileA failed %lu\n", GetLastError());
        return packed; /* Returning packed, the module is loaded so it must exists on disk */
    }

    hMapFile = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, "exeScanner");
    if (!hMapFile)
    {
        cli_dbgmsg("exeScanner: CreateFileMappingA() failed %lu\n", GetLastError());
        goto cleanup;
    }

    lpMapAddress = (LPBYTE) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
    if (!lpMapAddress)
    {
        cli_dbgmsg("exeScanner: MapViewOfFile() failed %lu\n", GetLastError());
        goto cleanup;
    }

    seek = lpMapAddress;

    /* DOS Signature 'MZ' */
    FILLBYTES(e_mz);
    if (e_mz != IMAGE_DOS_SIGNATURE)
    {
        cli_dbgmsg("exeScanner: DOS Signature not found\n");
        goto cleanup;
    }

    seek += 0x3c;

    FILLBYTES(e_lfanew);
    if (!e_lfanew)
    {
        cli_dbgmsg("exeScanner: Invalid PE offset\n");
        goto cleanup;
    }
    seek = lpMapAddress + e_lfanew;

    /* PE Signature 'PE' */
    FILLBYTES(e_magic);
    if (e_magic != IMAGE_NT_SIGNATURE)
    {
        cli_dbgmsg("exeScanner: PE Signature not found\n");
        goto cleanup;
    }
    seek += sizeof(e_magic);

    if (IsBadReadPtr(seek, sizeof(IMAGE_FILE_HEADER))) goto cleanup;
    pehdr = (PIMAGE_FILE_HEADER) seek;
    seek += sizeof(IMAGE_FILE_HEADER);

    if (IsBadReadPtr(seek, sizeof(IMAGE_OPTIONAL_HEADER32))) goto cleanup;
    opthdr = (PIMAGE_OPTIONAL_HEADER32) seek;
    seek += sizeof(IMAGE_OPTIONAL_HEADER32);

    if (pehdr->Machine != IMAGE_FILE_MACHINE_I386)
    {
        cli_dbgmsg("exeScanner: Not an x86 executable\n");
        goto cleanup;
    }

    /* Invalid sections number */
    if ((pehdr->NumberOfSections < 1) || (pehdr->NumberOfSections > 32))
    {
        cli_dbgmsg("exeScanner: Invalid sections number\n");
        packed = 1;
        goto cleanup;
    }

    for (i = 0; i < pehdr->NumberOfSections; i++)
    {
        double section_entropy = .0f;
        if (IsBadReadPtr(seek, sizeof(IMAGE_SECTION_HEADER))) goto cleanup;
        sechdr = (PIMAGE_SECTION_HEADER) seek;
        seek += sizeof(IMAGE_SECTION_HEADER);

        if (opthdr->AddressOfEntryPoint >= sechdr->VirtualAddress)
            epoff = opthdr->AddressOfEntryPoint - sechdr->VirtualAddress + sechdr->PointerToRawData;

        s_start = lpMapAddress + sechdr->PointerToRawData;
        if (!IsBadReadPtr(s_start, sechdr->SizeOfRawData))
            section_entropy = calc_entropy(s_start, sechdr->SizeOfRawData);

        entropy = MAX(entropy, section_entropy);

        /* Sanitize the section name */
        memcpy(secname, sechdr->Name, IMAGE_SIZEOF_SHORT_NAME);
        for (c = 0; (c < IMAGE_SIZEOF_SHORT_NAME) && secname[c]; c++)
            if (!isprint(secname[c])) secname[c] = '?';
        secname[IMAGE_SIZEOF_SHORT_NAME - 1] = 0;

        cli_dbgmsg("exeScanner: Section name: [%s] - Entropy %f\n", secname, section_entropy);

        if (!sechdr->SizeOfRawData)
            badsection = 1;
    }

    cli_dbgmsg("exeScanner: Max entropy = %f\n", entropy);
    /* EP Check */
    cli_dbgmsg("exeScanner: Entry Point rva: 0x%lx - raw: 0x%lx\n", opthdr->AddressOfEntryPoint, epoff);

    ep = lpMapAddress + epoff;
    if (!IsBadReadPtr(ep, EP_SIGNATURE_SIZE))
    {
#ifdef DUMP_SIGNATURE
        int i;
        for (i = 0; i < EP_SIGNATURE_SIZE; i++)
            cli_dbgmsg("%02x ", ep[i]);
        cli_dbgmsg("\n[C Code]: ");
        for (i = 0; i < EP_SIGNATURE_SIZE - 1; i++)
            cli_dbgmsg("0x%02x, ", ep[i]);
        cli_dbgmsg("0x%02x\n", ep[i]);
#endif
        if ((sig = checksig(ep)))
        {
            cli_dbgmsg("exeScanner: Signature check: %s\n", sig->name);
            entropy += sig->score;
            packed = (sig->score >= .0f);
            if (sig->score < .0f)
                cli_dbgmsg("exeScanner: Whitelisted signature found, lowering entropy to %f\n", entropy);
        }
        else
            cli_dbgmsg("exeScanner: Signature check: Nothing found\n");
    }
    else
        cli_dbgmsg("exeScanner: Invalid address of Entry Point\n");

    if (badsection)
    {
        if ((entropy == .0f) || (entropy > ENTROPY_THRESHOLD))
        {
            cli_dbgmsg("exeScanner: found zero SizeOfRawData and entropy %f\n", entropy);
            packed = 1;
            goto cleanup;
        }
    }

cleanup:
    if (lpMapAddress) UnmapViewOfFile(lpMapAddress);
    if (hMapFile) CloseHandle(hMapFile);
    CloseHandle(hFile);
    return packed;
}
